<

Flutter のキーボード フォーカス システムを理解する

この記事では、キーボード入力の方向を制御する方法について説明します。もし、あんたが ほとんどの場合、物理キーボードを使用するアプリケーションを実装しています。 デスクトップおよび Web アプリケーションの場合、このページが最適です。アプリが使用されない場合 物理キーボードを使用する場合は、これをスキップできます。

概要

Flutter には、キーボード入力をターゲットに向けるフォーカス システムが付属しています。 アプリケーションの特定の部分。これを行うために、ユーザーは入力に「集中」します。 目的の UI 要素をタップまたはクリックして、アプリケーションのその部分に移動します。 それが起こると、キーボードで入力されたテキストがその部分に流れます。 フォーカスがアプリケーションの別の部分に移動するまで、アプリケーションを押し続けます。フォーカスできる 特定のキーボード ショートカットを押して移動することもできます。通常は、 に縛られているタブキーを使用するため、「タブトラバーサル」と呼ばれることもあります。

このページでは、Flutter でこれらの操作を実行するために使用される API について説明します。 アプリケーション、およびフォーカス システムの仕組みについて説明します。私たちはいくつかのことがあることに気づきました 定義と使用方法について開発者の間で混乱が生じているFocusNodeオブジェクト。 これがあなたの経験に当てはまる場合は、次の項目に進んでください。~のベストプラクティス 作成FocusNodeオブジェクト

ユースケースに焦点を当てる

フォーカスの使用方法を知る必要がある状況の例 システム:

用語集

以下は、Flutter が使用するフォーカス システムの要素の用語です。の これらの概念の一部を実装するさまざまなクラスを以下に紹介します。

  • フォーカスツリー- 通常、まばらにミラーリングするフォーカス ノードのツリー ウィジェット ツリー。フォーカスを受け取ることができるすべてのウィジェットを表します。
  • フォーカスノード- フォーカス ツリー内の単一ノード。このノードは、 フォーカスであり、フォーカス チェーンの一部である場合は「フォーカスがある」と言われます。それ フォーカスがある場合にのみ主要なイベントの処理に参加します。
  • 主な焦点- フォーカス ツリーのルートから最も遠いフォーカス ノード それは焦点があります。これは、主要なイベントが伝播を開始するフォーカス ノードです。 プライマリ フォーカス ノードとその祖先。
  • フォーカスチェーン- プライマリから始まるフォーカス ノードの順序付きリスト フォーカス ノードに移動し、フォーカス ツリーのブランチをたどってそのルートに到達します。 フォーカスツリー。
  • フォーカス範囲- ジョブのグループを含める特別なフォーカス ノード 他のフォーカス ノードにアクセスし、それらのノードのみがフォーカスを受け取ることを許可します。を含む サブツリー内で以前にどのノードがフォーカスされていたかに関する情報。
  • フォーカストラバーサル- フォーカス可能な 1 つのノードから次のノードに移動するプロセス もう一つは予測可能な順序で。これは通常、次のようなアプリケーションで発生します。 ユーザーが押すタブキーを押して次のフォーカス可能なコントロールに移動するか、 分野。

FocusNode と FocusScopeNode

FocusNodeFocusScopeNodeオブジェクトは フォーカスシステムの仕組み。これらは存続期間の長いオブジェクトです (ウィジェットよりも長く、 レンダー オブジェクトと同様) はフォーカス状態と属性を保持するため、 ウィジェット ツリーのビルド間で永続的になります。一緒に、彼らは形成します フォーカスツリーのデータ構造。

これらは元々、制御に使用される開発者向けのオブジェクトであることを意図していました。 フォーカス システムのいくつかの側面は、時間の経過とともにほとんどの点に進化しました。 フォーカス システムの詳細を実装します。現状を壊さないようにするために アプリケーションには、その属性のパブリック インターフェイスが含まれています。しかし、 一般に、それらが最も役立つのは、相対的な役割を果たすことです。 不透明なハンドル。呼び出すために子孫ウィジェットに渡されます。requestFocus()祖先ウィジェットで、子孫ウィジェットがフォーカスを取得することを要求します。 他の属性の設定は、FocusまたFocusScopeウィジェットを使用していない場合、または独自のウィジェットを実装していない場合 それらのバージョン。

FocusNode オブジェクトを作成するためのベスト プラクティス

これらのオブジェクトの使用に関する注意事項には、次のようなものがあります。

  • 新しいものを割り当てないでくださいFocusNodeビルドごとに。これにより発生する可能性があるのは、 メモリ リークが発生し、ウィジェットの実行時にフォーカスが失われることがあります。 ノードにフォーカスがある間に再構築します。
  • 作成してくださいFocusNodeFocusScopeNodeステートフル ウィジェット内のオブジェクト。FocusNodeFocusScopeNode使い終わったら処分する必要がある それらを使用するため、ステートフル ウィジェットの内部でのみ作成する必要があります。 オーバーライドできる状態オブジェクトdisposeそれらを処分するために。
  • 同じものを使用しないでくださいFocusNode複数のウィジェットの場合。そうする場合、 ウィジェットはノードの属性の管理をめぐって争うことになります。 おそらく期待したものは得られないでしょう。
  • を設定してくださいdebugLabel診断に役立つフォーカス ノード ウィジェットの 焦点の問題。
  • を設定しないでくださいonKeyのコールバックFocusNodeまたFocusScopeNodeもしも 彼らはによって管理されていますFocusまたFocusScopeウィジェット。ご希望の場合は、onKeyハンドラーを作成し、新しいハンドラーを追加しますFocusウィジェット サブツリーの周りのウィジェット を聞いて設定したいonKeyウィジェットの属性を ハンドラ。設定canRequestFocus: false不要な場合はウィジェット上でも それは主に焦点を当てることができるためです。これは、onKey属性 でFocusウィジェットは後続のビルドで別のものに設定できます。 そうなると、上書きされます。onKeyノードに設定したハンドラー。
  • 電話してくださいrequestFocus()ノード上で、 主な焦点、特に所有するノードを渡した祖先からの焦点 注目したい子孫。
  • 使ってくださいfocusNode.requestFocus()。電話する必要はありませんFocusScope.of(context).requestFocus(focusNode)。のfocusNode.requestFocus()メソッドは同等であり、よりパフォーマンスが高くなります。

焦点がぼやける

ノードに「フォーカスを放棄する」ように指示するための API があります。FocusNode.unfocus()。ノードからフォーカスは外されますが、重要なことは すべてのノードを「フォーカスを外す」などということは実際には存在しないことを理解してください。もし ノードがフォーカスされていない場合は、他の場所にフォーカスを渡す必要があります。いつも主な焦点。ノードが呼び出したときにフォーカスを受け取るノードunfocus()最も近いのはFocusScopeNode、または以前にフォーカスされたノード その範囲内で、dispositionに与えられた引数unfocus()。 フォーカスを削除したときにフォーカスがどこに移動するかをより詳細に制御したい場合は、 ノード、呼び出す代わりに別のノードに明示的にフォーカスするunfocus()、または 別のノードを見つけるためのフォーカス トラバーサル メカニズムfocusInDirectionnextFocus、 またpreviousFocusのメソッドFocusNode

電話をかけるときunfocus()disposition引数では 2 つのモードが許可されます 焦点が合っていない:UnfocusDisposition.scopeUnfocusDisposition.previouslyFocusedChild。デフォルトはscope、それは与える フォーカスを最も近い親フォーカス スコープに移動します。これは、焦点が その後、次のノードに移動しますFocusNode.nextFocusで始まります。 スコープ内の「最初の」フォーカス可能な項目。

previouslyFocusedChild性質はスコープを検索して 以前にフォーカスしていた子にフォーカスを要求します。以前にない場合 集中した子、これは以下と同等ですscope

フォーカスウィジェット

Focusウィジェットはフォーカス ノードを所有および管理し、 フォーカスシステム。所有するフォーカス ノードのアタッチとデタッチを管理します。 フォーカス ツリーからフォーカス ノードの属性とコールバックを管理し、 ウィジェットにアタッチされたフォーカス ノードの検出を可能にする静的関数があります。 木。

最も単純な形では、Focusウィジェット サブツリーの周りのウィジェットでは、 フォーカストラバーサルプロセスの一部としてフォーカスを取得するためにそのウィジェットサブツリー、または いつでもrequestFocusに呼び出されますFocusNodeそれに渡されました。組み合わせると を呼び出すジェスチャ検出器を使用requestFocus、次の場合にフォーカスを受け取ることができます タップまたはクリックされました。

合格するかもしれませんFocusNodeに反対するFocusウィジェットを管理する必要がありますが、 そうしないと、独自のものが作成されます。自分で作成する主な理由FocusNode電話できることですrequestFocus()ノード上で親ウィジェットからフォーカスを制御します。その他のほとんどは の機能FocusNodeの属性を変更することでアクセスするのが最適です。 のFocusウィジェット自体。

Focusウィジェットは、Flutter 独自のコントロールのほとんどで、そのコントロールを実装するために使用されます。 フォーカス機能。

以下に、その使用方法を示す例を示します。Focusカスタムを作成するウィジェット コントロールはフォーカス可能です。受信に反応するテキストを含むコンテナを作成します。 集中。

import 'package:flutter/material.dart';

void main() => runApp(const MyApp());

class MyApp extends StatelessWidget {
  const MyApp({super.key});
  static const String _title = 'Focus Sample';

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: _title,
      home: Scaffold(
        appBar: AppBar(title: const Text(_title)),
        body: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: const <Widget>[MyCustomWidget(), MyCustomWidget()],
        ),
      ),
    );
  }
}

class MyCustomWidget extends StatefulWidget {
  const MyCustomWidget({super.key});

  @override
  State<MyCustomWidget> createState() => _MyCustomWidgetState();
}

class _MyCustomWidgetState extends State<MyCustomWidget> {
  Color _color = Colors.white;
  String _label = 'Unfocused';

  @override
  Widget build(BuildContext context) {
    return Focus(
      onFocusChange: (focused) {
        setState(() {
          _color = focused ? Colors.black26 : Colors.white;
          _label = focused ? 'Focused' : 'Unfocused';
        });
      },
      child: Center(
        child: Container(
          width: 300,
          height: 50,
          alignment: Alignment.center,
          color: _color,
          child: Text(_label),
        ),
      ),
    );
  }
}

重要なイベント

サブツリー内の主要なイベントをリッスンする場合は、onKeyの属性 のFocusウィジェットをキーをリッスンするだけのハンドラーにするか、 キーを処理し、他のウィジェットへの伝播を停止します。

キー イベントは、プライマリ フォーカスを持つフォーカス ノードから始まります。そのノードがそうでない場合 戻るKeyEventResult.handledそのからonKeyハンドラー、次にその親フォーカス ノードにイベントが与えられます。親が処理しない場合は、その親に送られます。 以下同様に、フォーカス ツリーのルートに到達するまで続きます。イベントが次の期間に到達した場合、 フォーカス ツリーのルートが処理されずに返される アプリケーション内の次のネイティブ コントロールに与えるプラットフォーム ( Flutter UI は、より大きなネイティブ アプリケーション UI の一部です)。取り扱うイベント 他の Flutter ウィジェットには伝播されません。また、他の Flutter ウィジェットにも伝播されません。ああ ネイティブウィジェット。

以下にその例を示しますFocusサブツリー内のすべてのキーを吸収するウィジェット 主な焦点となることができなければ、処理できません。

@override
Widget build(BuildContext context) {
  return Focus(
    onKey: (node, event) => KeyEventResult.handled,
    canRequestFocus: false,
    child: child,
  );
}

フォーカス キー イベントはテキスト入力イベントの前に処理されるため、キー イベントを処理します。 フォーカス ウィジェットがテキスト フィールドを囲むと、そのキーが表示されなくなります。 テキストフィールドに入力されました。

これは、文字「a」の入力を許可しないウィジェットの例です。 テキストフィールド:

@override
Widget build(BuildContext context) {
  return Focus(
    onKey: (node, event) {
      return (event.logicalKey == LogicalKeyboardKey.keyA)
          ? KeyEventResult.handled
          : KeyEventResult.ignored;
    },
    child: TextField(),
  );
}

意図が入力検証である場合、この例の機能はおそらく次のようになります。 を使用してより適切に実装できますTextInputFormatter、しかし、このテクニックはまだ可能です 役に立ちます:Shortcutsウィジェットはこのメソッドを使用して、前にショートカットを処理します。 たとえば、テキスト入力になります。

何をフォーカスするかを制御する

フォーカスの主な側面の 1 つは、何をどのようにフォーカスできるかを制御することです。 属性canRequestFocusskipTraversal,descendantsAreFocusableこのノードとその子孫がフォーカス プロセスにどのように参加するかを制御します。

もしベッド09bb4-1620-4880-8c14-8e09ec53f889属性が true の場合、このフォーカス ノードは参加しません フォーカストラバーサル中。場合でもフォーカス可能ですrequestFocusその上で呼び出されます フォーカス ノードですが、それ以外の場合はフォーカス トラバーサル システムが検索しているときにスキップされます。 次に集中すべきことのために。

canRequestFocus当然のことながら、属性は、 これがフォーカスノードFocusウィジェット管理を使用してフォーカスを要求できます。もしも この属性が false の場合、呼び出しますrequestFocusノード上では効果がありません。 また、このノードはフォーカス トラバーサルができないため、スキップされることも意味します。 フォーカスを要求します。

descendantsAreFocusable属性は、この子孫かどうかを制御します ノードはフォーカスを受け取ることができますが、引き続きこのノードがフォーカスを受け取ることを許可します。これ 属性を使用すると、ウィジェットのサブツリー全体のフォーカス機能をオフにすることができます。 このようにして、ExcludeFocusウィジェットは機能します: それはただのFocusウィジェット付き この属性セット。

オートフォーカス

の設定autofocusの属性Focusウィジェットはウィジェットに次のことを指示します 属するフォーカス スコープが初めてフォーカスされたときに、フォーカスを要求します。もしも 複数のウィジェットがありますautofocus設定されている場合、どちらにするかは任意です はフォーカスを受け取るため、フォーカス スコープごとに 1 つのウィジェットにのみ設定するようにしてください。

のadb0150f-47a2-4d00-牛肉-f77cf0e72006属性は、まだフォーカスがない場合にのみ有効です。 ノードが属するスコープ。

の設定autofocus異なるフォーカスに属する 2 つのノードの属性 スコープは明確に定義されています。スコープが指定されたときに、それぞれがフォーカスされたウィジェットになります。 対応するスコープがフォーカスされます。

変更通知

Focus.onFocusChangedコールバックを使用して、 特定のノードのフォーカス状態が変更されました。ノードが追加された場合に通知します フォーカス チェーンに追加したり、フォーカス チェーンから削除したりする場合でも、通知を受け取ります。 それは主な焦点ではありません。受け取ったかどうかだけを知りたい場合は、 主な焦点、チェックしてくださいhasPrimaryFocusフォーカスノードでは true です。

FocusNode の取得

場合によっては、オブジェクトのフォーカス ノードを取得すると便利です。Focusウィジェットへ その属性を調べます。

の祖先からフォーカス ノードにアクセスするには、Focusウィジェット、作成して渡す でFocusNodeとしてFocusウィジェットのfocusNode属性。必要だから 破棄するには、渡したフォーカス ノードがステートフル ノードによって所有されている必要があります。 ウィジェットを構築するたびにウィジェットを作成するだけではありません。

子孫からフォーカス ノードにアクセスする必要がある場合は、Focusウィジェット、 電話できますFocus.of(context)最も近いフォーカスノードを取得するにはFocus ウィジェットを指定されたコンテキストに追加します。を取得する必要がある場合は、FocusNodeFocus同じビルド関数内のウィジェットの場合は、Builder持っていることを確認するために 正しいコンテキスト。これを次の例に示します。

@override
Widget build(BuildContext context) {
  return Focus(
    child: Builder(
      builder: (context) {
        final bool hasPrimary = Focus.of(context).hasPrimaryFocus;
        print('Building with primary focus: $hasPrimary');
        return const SizedBox(width: 100, height: 100);
      },
    ),
  );
}

タイミング

フォーカス システムの詳細の 1 つは、フォーカスが要求されたときのみ、 現在のビルドフェーズが完了した後に有効になります。これは集中力を意味します フォーカスの変更により変更が常に 1 フレーム遅れます。 ウィジェット ツリーの任意の部分 (その祖先を含む) を再構築します。 現在フォーカスを要求しているウィジェット。子孫が自分たちを汚してはいけないから 先祖のように、必要な変更ができるように、フレーム間で実行する必要があります。 次のフレームで起こります。

フォーカススコープウィジェット

FocusScopeウィジェットはの特別バージョンですFocus管理するウィジェット あるFocusScopeNodeの代わりにFocusNode。のFocusScopeNode特別です フォーカス ノードのグループ化メカニズムとして機能するフォーカス ツリー内のノード サブツリー内。ノードが外側にない限り、フォーカス トラバーサルはフォーカス スコープ内に留まります。 スコープのフォーカスが明示的に設定されています。

フォーカス スコープは、ノードの現在のフォーカスと履歴も追跡します。 そのサブツリー内に焦点を当てます。そうすることで、ノードがフォーカスを解放するか削除された場合に、 フォーカスがある場合は、フォーカスがあったノードにフォーカスを戻すことができます。 以前。

フォーカス スコープは、子孫が存在しない場合にフォーカスを戻す場所としても機能します。 集中力がある。これにより、フォーカス トラバーサル コードに開始コンテキストを含めることができます。 移動先の次 (または最初) のフォーカス可能なコントロールを見つけます。

フォーカス スコープ ノードをフォーカスすると、まず、現在またはほとんどのノードにフォーカスしようとします。 サブツリー内の最近フォーカスされたノード、またはリクエストを行ったサブツリー内のノード オートフォーカス (ある場合)。そのようなノードがない場合は、そのノード自体がフォーカスを受け取ります。

FocusableActionDetector ウィジェット

FocusableActionDetectorの機能を組み合わせたウィジェットです。ActionsShortcutsMouseRegionそしてFocus作成するウィジェット アクションとキー バインディングを定義し、コールバックを提供する検出器 フォーカスとホバーハイライトの処理。これは、Flutter コントロールが次の目的で使用するものです。 コントロールのこれらの側面をすべて実装します。を使用して実装されるだけです 構成要素のウィジェットなので、すべての機能が必要ない場合は、 必要なものを使用してください。ただし、これらの動作を組み込むのに便利な方法です。 カスタムコントロール。

フォーカストラバーサルの制御

アプリケーションがフォーカスできるようになると、次に多くのアプリケーションが望むことになります。 ユーザーがキーボードまたは別の入力を使用してフォーカスを制御できるようにすることです デバイス。この最も一般的な例は「タブ トラバーサル」です。 を押しますタブキーを押して「次の」コントロールに進みます。 「次」をコントロールする 手段がこのセクションの主題です。この種のトラバースは次によって提供されます。 デフォルトでは flutterします。

シンプルなグリッド レイアウトでは、次のコントロールを決定するのは非常に簡単です。もしも あなたが列の最後尾ではない場合、それは右(または左)の列です 右から左へのロケール)。行の最後にいる場合は、それが最初のコントロールです 次の行にあります。残念ながら、アプリケーションがグリッドに配置されることはほとんどありません。 多くの場合、さらなる指導が必要になります。

Flutter のデフォルトのアルゴリズム (ReadingOrderTraversalPolicy) 焦点を合わせるため トラバーサルは非常に優れており、ほとんどのアプリケーションに対して正しい答えが得られます。 ただし、病的なケースや、状況や状況が異なるケースは常に存在します。 設計には、デフォルトの順序付けアルゴリズムとは異なる順序が必要です に到着します。そのような場合には、次のことを達成するための他のメカニズムがあります。 希望の順序。

FocusTraversalGroup ウィジェット

FocusTraversalGroupウィジェットはウィジェットの周囲のツリーに配置する必要があります 別のウィジェットまたは ウィジェットのグループ。多くの場合、ウィジェットを関連グループにグループ化するだけで十分です。 多くのタブトラバーサル順序の問題を解決します。そうでない場合は、グループを次のようにすることもできます。 与えられたFocusTraversalPolicyグループ内の順序を決定します。

デフォルトReadingOrderTraversalPolicy通常はこれで十分ですが、 順序をさらに制御する必要がある場合、OrderedTraversalPolicyに使える。のorderの議論FocusTraversalOrderフォーカス可能なコンポーネントの周囲にラップされたウィジェット 順序が決まります。順序は次の任意のサブクラスにすることができます。FocusOrder、 しかしNumericFocusOrderLexicalFocusOrder提供されています。

提供されているフォーカス トラバーサル ポリシーがいずれも十分でない場合は、 アプリケーションでは、独自のポリシーを作成し、それを使用して任意のポリシーを決定することもできます。 ご希望のカスタムオーダー。

使用方法の例は次のとおりですFocusTraversalOrderを横断するウィジェット ボタンを 2、1、3 の順序で並べます。NumericFocusOrder

class OrderedButtonRow extends StatelessWidget {
  const OrderedButtonRow({super.key});

  @override
  Widget build(BuildContext context) {
    return FocusTraversalGroup(
      policy: OrderedTraversalPolicy(),
      child: Row(
        children: <Widget>[
          const Spacer(),
          FocusTraversalOrder(
            order: NumericFocusOrder(2),
            child: TextButton(
              child: const Text('ONE'),
              onPressed: () {},
            ),
          ),
          const Spacer(),
          FocusTraversalOrder(
            order: NumericFocusOrder(1),
            child: TextButton(
              child: const Text('TWO'),
              onPressed: () {},
            ),
          ),
          const Spacer(),
          FocusTraversalOrder(
            order: NumericFocusOrder(3),
            child: TextButton(
              child: const Text('THREE'),
              onPressed: () {},
            ),
          ),
          const Spacer(),
        ],
      ),
    );
  }
}

フォーカストラバーサルポリシー

FocusTraversalPolicy次のウィジェットを決定するオブジェクトです。 指定されたリクエストと現在のフォーカス ノード。リクエスト (メンバー関数) は次のとおりです。 のようなものfindFirstFocusfindLastFocusnextprevious、 とinDirection

FocusTraversalPolicyは、次のような具体的なポリシーの抽象基本クラスです。ReadingOrderTraversalPolicyOrderedTraversalPolicyそしてそのDirectionalFocusTraversalPolicyMixinクラス。

を使用するにはFocusTraversalPolicy、あなたはaに1つを与えますFocusTraversalGroup、ポリシーが含まれるウィジェットのサブツリーを決定します。 効果的になります。クラスのメンバー関数が直接呼び出されることはほとんどありません。 これらはフォーカス システムによって使用されることを目的としています。

フォーカスマネージャー

FocusManagerシステムの現在の主な焦点を維持します。それ フォーカス システムのユーザーに役立つ API がいくつかあるだけです。一 それはFocusManager.instance.primaryFocusプロパティ。これには、 現在フォーカスされているフォーカス ノード。グローバルからもアクセス可能primaryFocus分野。

その他の便利なプロパティは次のとおりです。FocusManager.instance.highlightModeFocusManager.instance.highlightStrategy。これらは、必要なウィジェットによって使用されます。 「タッチ」モードと「従来の」(マウスとキーボード)モードを切り替えるには 彼らの注目のハイライトについて。ユーザーがタッチを使用してナビゲートしている場合、フォーカスは 通常、ハイライトは非表示になっており、マウスまたはキーボードに切り替えると、 何がフォーカスされているかがわかるように、フォーカス ハイライトを再度表示する必要があります。のhightlightStrategyフォーカス マネージャーに変更を解釈する方法を指示します。 デバイスの使用モード: 2 つのモードを自動的に切り替えることができます。 最新の入力イベントに基づいて、またはタッチでロックすることも、 従来のモード。 Flutter で提供されているウィジェットは、これの使用方法をすでに知っています。 情報なので、独自のコントロールを作成する場合にのみ必要になります。 傷。使用できますaddHighlightModeListener変更をリッスンするコールバック ハイライトモードで。